#ifndef LOADER_H_
#define LOADER_H_

#include <glibmm.h>
#include <boost/shared_ptr.hpp>
#include <boost/ptr_container/ptr_list.hpp>

#include "conf/parser.h"
#include "misc.h"
#include "url.h"

#ifdef HAVE_BOOST_ASIO
# include <boost/asio.hpp>
# define ASIO_NS boost::asio
#else
# include <asio.hpp>
# define ASIO_NS asio
#endif

#ifdef USE_GIO
# include <gio/gio.h>
# define ENABLE_FILE_SCHEME
#endif

namespace gpyp {

class Conf;

namespace loader {

class Dispatcher : boost::noncopyable {
public:
  typedef sigc::slot<void> Slot;

private:
  struct Data {
    Dispatcher *self;
    guint       id;
    inline Data(Dispatcher &s) :
      self(&s),
      id  (0)
    {
    }
  };

  Slot                  slot_;
  boost::ptr_list<Data> data_;
  GMutex               *mutex_;

  static gboolean on_idle(Data *data);

public:
  void emit();

  inline void connect(Slot slot) {
    slot_ = slot;
  }
  inline void disconnect() {
    slot_ = Slot();
  }

  void stop();

  Dispatcher();
  virtual ~Dispatcher();
};

class Request {
private:
  URL         url_;
  std::string charset_;

public:
  inline const URL &get_url() const {
    return url_;
  }
  inline void set_url(const URL &url) {
    url_ = url;
  }

  inline const std::string &get_charset() const {
    return charset_;
  }
  inline void set_charset(const std::string &charset) {
    charset_ = charset;
  }

private:
  Request &operator=(const Request &req);

protected:
  inline Request(const URL &url) :
    url_(url)
  {
  }
  inline Request(const Request &req) :
    url_    (req.url_),
    charset_(req.charset_)
  {
  }
  inline virtual ~Request() { }
};
typedef boost::shared_ptr<Request> RefRequest;

class Response : boost::noncopyable {
private:
  conf::Source source_;

public:
  inline conf::Source &get_source() {
    return source_;
  }
  inline const conf::Source &get_source() const {
    return source_;
  }

protected:
  inline Response() { }
  inline virtual ~Response() { }
};
typedef boost::shared_ptr<Response> RefResponse;

class Error;
typedef boost::shared_ptr<Error> RefError;
class Error : boost::noncopyable {
private:
  bool          set_;
  Glib::ustring message_;

public:
  inline bool is_set() const {
    return set_;
  }

  inline void set_message(const Glib::ustring &message) {
    set_     = true;
    message_ = message;
  }
  inline const Glib::ustring &get_message() const {
    return message_;
  }

  inline static RefError create() {
    return RefError(new Error());
  }
  inline static RefError create(const Glib::ustring &message) {
    return RefError(new Error(message));
  }

protected:
  inline Error() :
    set_(false)
  {
  }
  inline Error(const Glib::ustring &message) :
    set_    (true),
    message_(message)
  {
  }
public:
  inline virtual ~Error() { }
};

class Connection;
typedef boost::shared_ptr<Connection> RefConnection;
class Connection : boost::noncopyable {
public:
  typedef sigc::slot<void, const RefError> SlotError;
  typedef sigc::slot<void> SlotEnd;
  typedef sigc::slot<void> SlotJoined;

  static const int TIMEOUT_DEFAULT;

protected:
  Application      &app_;

private:
  RefRequest        request_;
  RefResponse       response_;

  bool              dirty_;
  bool              sync_;

protected:
  Dispatcher        dispatcher_complete_;
  Dispatcher        dispatcher_error_;
  Dispatcher        dispatcher_join_;
  SlotJoined        slot_joined_;

private:
  int               timeout_time_;
  sigc::connection  timeout_connection_;
  int               sync_timer_;
  Glib::Mutex       sync_mutex_;

protected:
  bool              loading_;
  bool              timeout_;
  bool              cancelled_;

private:
  RefError          error_;

  RefConnection     next_;

protected:
  void on_join();
  void on_load_thread();
  virtual void on_load() = 0;
  virtual void on_cancel();

  virtual void on_destroy();

  void timer_thread();

public:
  inline const RefRequest get_request() const {
    return request_;
  }
  inline const RefResponse get_response() const {
    return response_;
  }

  inline int get_timeout() const {
    return timeout_time_;
  }
  inline void set_timeout(int timeout) {
    timeout_time_ = timeout;
  }

  inline bool is_loading() const {
    return loading_;
  }

  void load(bool sync = false);
  void cancel();

  inline const RefConnection get_next() const {
    return next_;
  }
  inline void set_next(RefConnection conn) {
    next_ = conn;
  }

protected:
  void stop_timeout_timer(bool pause = false);
  void start_timeout_timer();

  bool timer_on_timeout();

  void complete(const char *data, size_t size);
  void error(const Glib::ustring &message);
  void join();

  Connection(Application &app,
             RefRequest   request,
             RefResponse  response,
             SlotError    slot_error,
             SlotJoined   slot_joined);
  virtual ~Connection();
};

class HTTPHeaderMap : public std::map<std::string, std::string> {
public:
  inline iterator find(const std::string &key) {
    for(iterator iter = begin(); iter != end(); ++iter) {
      if (strcasecmp(iter->first.c_str(), key.c_str()) == 0) return iter;
    }
    return end();
  }
  inline const_iterator find(const std::string &key) const {
    for(const_iterator iter = begin(); iter != end(); ++iter) {
      if (strcasecmp(iter->first.c_str(), key.c_str()) == 0) return iter;
    }
    return end();
  }

  inline bool has_key(const std::string &key) const {
    return find(key) != end();
  }

  inline iterator set(const std::string &key,
                      const std::string &val) {
    iterator iter = find(key);
    if (iter == end()) {
      return insert(end(), std::make_pair(key, val));
    } else {
      iter->second = val;
      return iter;
    }
  }

  inline void write_to(std::ostream &s, const char *ln = "\r\n") const {
    bool first = true;
    for(const_iterator iter = begin(); iter != end(); ++iter) {
      if (first) {
        first = false;
      } else {
        s << ln;
      }
      s << iter->first << ": " << iter->second;
    }
  }

  inline HTTPHeaderMap() { }
};

class HTTPRequest;
typedef boost::shared_ptr<HTTPRequest> RefHTTPRequest;
class HTTPRequest : public Request {
public:
  enum Method {
    METHOD_INVALID,
    METHOD_GET,
    METHOD_POST,
  };

private:
  Method        method_;
  HTTPHeaderMap headers_;
  std::string   post_data_;

public:
  inline Method get_method() const {
    return method_;
  }
  inline void set_method(Method method) {
    method_ = method;
  }

  inline const HTTPHeaderMap &get_headers() const {
    return headers_;
  }
  inline void set_header(const std::string &key,
                         const std::string &val) {
    headers_.set(key, val);
  }

  inline bool get_has_auth() const {
    return headers_.has_key("Authorization");
  }
  inline void set_auth_basic(const std::string &id, const std::string &pw) {
    set_header("Authorization", "Basic " + URL::create_auth_base64(&id, &pw));
  }

  inline const std::string &get_post_data() const {
    return post_data_;
  }
  inline void set_post_data(const std::string &post_data) {
    post_data_ = post_data;
  }

  inline static RefHTTPRequest create(Method method, const URL &url) {
    return RefHTTPRequest(new HTTPRequest(method, url));
  }
  inline static RefHTTPRequest create(const URL &url) {
    return RefHTTPRequest(new HTTPRequest(METHOD_GET, url));
  }
  inline static RefHTTPRequest create(const URL         &url,
                                      const std::string &post_data) {
    RefHTTPRequest req(new HTTPRequest(METHOD_POST, url));
    req->set_post_data(post_data);
    return req;
  }

  inline RefHTTPRequest copy() {
    return RefHTTPRequest(new HTTPRequest(*this));
  }

  std::ostream &write_to(std::ostream &s,
                         const char   *ln = "\r\n") const;

  inline std::string to_string(const char *ln = "\r\n") const {
    std::stringstream ss;
    write_to(ss, ln);
    return ss.str();
  }

protected:
  HTTPRequest(Method method, const URL &url);
  HTTPRequest(const HTTPRequest &req);
public:
  inline virtual ~HTTPRequest() { }
};

class HTTPResponse;
typedef boost::shared_ptr<HTTPResponse> RefHTTPResponse;
class HTTPResponse : public Response {
private:
  std::string   http_version_;
  int           code_;
  std::string   message_;
  HTTPHeaderMap headers_;

  std::string   location_;

  std::string   auth_scheme_;
  std::map<std::string, std::string> auth_params_;

public:
  inline void set_response(const std::string &http_version,
                           int                code,
                           const std::string &message) {
    http_version_ = http_version;
    code_         = code;
    message_      = message;
  }

  inline const std::string &get_http_version() const {
    return http_version_;
  }
  inline int get_code() const {
    return code_;
  }
  inline const std::string &get_message() const {
    return message_;
  }
  inline HTTPHeaderMap &get_headers() {
    return headers_;
  }
  inline const HTTPHeaderMap &get_headers() const {
    return headers_;
  }

  inline bool is_redirected() const {
    return (code_ / 100) == 3 && !location_.empty();
  }
  inline const std::string &get_redirect_location() const {
    return location_;
  }

  inline bool is_auth_required() const {
    return code_ == 401;
  }
  inline const std::string &get_auth_scheme() const {
    return auth_scheme_;
  }

  inline bool set_header(const std::string &key,
                         const std::string &val) {
    HTTPHeaderMap::iterator iter = headers_.set(key, val);
    if (iter != headers_.end()) {
      if (strcasecmp(iter->first.c_str(), "Location") == 0) {
        location_ = iter->second;
      } else if (strcasecmp(iter->first.c_str(), "WWW-Authenticate") == 0) {
        const char *s = iter->second.c_str(), *c = s;
        for(; *c && *c != ' '; ++c) ;
        if (s < c) {
          auth_scheme_.assign(s, c);
          //for(; *c == ' '; ++c) ;
        }
      }
      return true;
    }
    return false;
  }

  inline static RefHTTPResponse create() {
    return RefHTTPResponse(new HTTPResponse());
  }

  inline std::ostream &write_to(std::ostream &s,
                                const char   *ln = "\r\n") const {
    s << http_version_ << " " << code_ << " " << message_ << ln;
    headers_.write_to(s, ln);
    return s;
  }

  inline std::string to_string(const char *ln = "\r\n") const {
    std::stringstream ss;
    write_to(ss, ln);
    return ss.str();
  }

protected:
  inline HTTPResponse() :
    code_(0)
  {
  }
public:
  inline virtual ~HTTPResponse() { }
};

class HTTPConnection;
typedef boost::shared_ptr<HTTPConnection> RefHTTPConnection;
class HTTPConnection : public Connection {
public:
  typedef sigc::slot<void, const RefHTTPResponse> SlotComplete;

private:
#ifdef HAVE_BOOST_ASIO
  typedef boost::system::error_code error_code;
#else
  typedef asio::error_code error_code;
#endif
  typedef ASIO_NS::ip::tcp::resolver::iterator resolve_iterator;

private:
  static Glib::StaticMutex _resolve_mutex;

  Glib::Mutex mutex_;

  RefHTTPRequest  request_;
  RefHTTPResponse response_;

  boost::shared_ptr<ASIO_NS::io_service>        io_service_;
  boost::shared_ptr<ASIO_NS::ip::tcp::resolver> resolver_;
  boost::shared_ptr<ASIO_NS::ip::tcp::socket>   socket_;
  boost::shared_ptr<ASIO_NS::streambuf>         request_buf_;
  boost::shared_ptr<ASIO_NS::streambuf>         response_buf_;

protected:
  virtual void on_load();
  virtual void on_cancel();

public:
  inline const RefHTTPRequest get_request() {
    return request_;
  }
  inline const RefHTTPResponse get_response() {
    return response_;
  }

  inline static
  RefHTTPConnection create(Application   &app,
                           const URL     &url,
                           SlotComplete   slot_complete = SlotComplete(),
                           SlotError      slot_error    = SlotError(),
                           SlotJoined     slot_joined   = SlotJoined()) {
    return RefHTTPConnection(new HTTPConnection(app,
                                                HTTPRequest::create(url),
                                                HTTPResponse::create(),
                                                slot_complete,
                                                slot_error,
                                                slot_joined));
  }
  inline static
  RefHTTPConnection create(Application   &app,
                           RefHTTPRequest request,
                           SlotComplete   slot_complete = SlotComplete(),
                           SlotError      slot_error    = SlotError(),
                           SlotJoined     slot_joined   = SlotJoined()) {
    return RefHTTPConnection(new HTTPConnection(app,
                                                request,
                                                HTTPResponse::create(),
                                                slot_complete,
                                                slot_error,
                                                slot_joined));
}

  inline void load() {
    Connection::load(false);
  }
  inline const RefHTTPResponse load_sync() {
    Connection::load(true);
    return response_;
  }

protected:
  HTTPConnection(Application    &app,
                 RefHTTPRequest  request,
                 RefHTTPResponse response,
                 SlotComplete    slot_complete,
                 SlotError       slot_error,
                 SlotJoined      slot_joined);
public:
  virtual ~HTTPConnection();
};

#ifdef ENABLE_FILE_SCHEME
class FileRequest;
typedef boost::shared_ptr<FileRequest> RefFileRequest;
class FileRequest : public Request {
public:
  inline static RefFileRequest create(const URL &url) {
    return RefFileRequest(new FileRequest(url));
  }

protected:
  FileRequest(const URL &url);
public:
  inline virtual ~FileRequest() { }
};

class FileResponse;
typedef boost::shared_ptr<FileResponse> RefFileResponse;
class FileResponse : public Response {
public:
  inline static RefFileResponse create() {
    return RefFileResponse(new FileResponse());
  }

protected:
  inline FileResponse() { }
public:
  inline virtual ~FileResponse() { }
};

class FileConnection;
typedef boost::shared_ptr<FileConnection> RefFileConnection;
class FileConnection : public Connection {
public:
  typedef sigc::slot<void, const RefFileResponse> SlotComplete;

private:
  Glib::Mutex   mutex_;
  GCancellable *cancellable_;

  RefFileRequest  request_;
  RefFileResponse response_;

protected:
  virtual void on_load();
  virtual void on_cancel();

public:
  inline static
  RefFileConnection create(Application   &app,
                           const URL     &url,
                           SlotComplete   slot_complete = SlotComplete(),
                           SlotError      slot_error    = SlotError(),
                           SlotJoined     slot_joined   = SlotJoined()) {
    return RefFileConnection(new FileConnection(app,
                                                FileRequest::create(url),
                                                FileResponse::create(),
                                                slot_complete,
                                                slot_error,
                                                slot_joined));
  }
  inline static
  RefFileConnection create(Application   &app,
                           RefFileRequest request,
                           SlotComplete   slot_complete = SlotComplete(),
                           SlotError      slot_error    = SlotError(),
                           SlotJoined     slot_joined   = SlotJoined()) {
    return RefFileConnection(new FileConnection(app,
                                                request,
                                                FileResponse::create(),
                                                slot_complete,
                                                slot_error,
                                                slot_joined));
  }

  inline void load() {
    Connection::load(false);
  }
  inline RefFileResponse load_sync() {
    Connection::load(true);
    return response_;
  }

protected:
  FileConnection(Application    &app,
                 RefFileRequest  request,
                 RefFileResponse response,
                 SlotComplete    slot_complete,
                 SlotError       slot_error,
                 SlotJoined      slot_joined);
public:
  virtual ~FileConnection();
};
#endif

class Pool : boost::noncopyable {
public:
  typedef sigc::slot<void, const RefResponse> SlotComplete;
  typedef Connection::SlotError SlotError;

private:
  static std::auto_ptr<Pool> _instance;
public:
  inline static void init(Application &app) {
    _instance.reset(new Pool(app));
  }
  inline static void uninit() {
    _instance.reset();
  }
  inline static
  RefHTTPConnection request(RefHTTPRequest req,
                            SlotComplete   slot_complete,
                            SlotError      slot_error) {
    return _instance->req(req, slot_complete, slot_error);
  }
#ifdef ENABLE_FILE_SCHEME
  inline static
  RefFileConnection request(RefFileRequest req,
                            SlotComplete   slot_complete,
                            SlotError      slot_error) {
    return _instance->req(req, slot_complete, slot_error);
  }
#endif
  inline static
  RefConnection request(const URL         &url,
                        SlotComplete       slot_complete,
                        SlotError          slot_error,
                        const std::string &charset = std::string()) {
    return _instance->req(url, slot_complete, slot_error, charset);
  }

  inline static
  RefHTTPResponse request_sync(RefHTTPRequest req) {
    return _instance->req_sync(req);
  }
#ifdef ENABLE_FILE_SCHEME
  inline static
  RefFileResponse request_sync(RefFileRequest req) {
    return _instance->req_sync(req);
  }
#endif
  inline static
  RefResponse request_sync(const URL         &url,
                           const std::string &charset) {
    return _instance->req_sync(url, charset);
  }

private:
  struct AutoContext {
    RefConnection connection_;
    SlotComplete  slot_complete_;
    SlotError     slot_error_;
  };
  typedef boost::ptr_list<AutoContext> ACList;

private:
  sigc::signal<void, const RefResponse> signal_complete_;
  sigc::signal<void, const RefError>    signal_error_;
public:
  inline sigc::signal<void, const RefResponse> signal_complete() {
    return signal_complete_;
  }
  inline sigc::signal<void, const RefError> signal_error() {
    return signal_error_;
  }

private:
  Application &app_;

  ACList auto_contexts_;

protected:
  void pre_http_request(RefHTTPRequest request);

  void get_auth(const URL &url, std::string &id, std::string &pw);
  RefHTTPRequest create_auth_request(RefRequest      request,
                                     RefHTTPResponse response,
                                     Glib::ustring  &error_message);

  void http_async_on_complete(const RefHTTPResponse response,
                              AutoContext          *ac);
  void conn_on_joined(AutoContext *ac);

  RefHTTPConnection req(RefHTTPRequest request,
                        SlotComplete   slot_complete,
                        SlotError      slot_error);
#ifdef ENABLE_FILE_SCHEME
  RefFileConnection req(RefFileRequest request,
                        SlotComplete   slot_complete,
                        SlotError      slot_error);
#endif
  RefConnection req(const URL         &url,
                    SlotComplete       slot_complete,
                    SlotError          slot_error,
                    const std::string &charset);

public:
  inline RefHTTPConnection req(RefHTTPRequest request) {
    return req(request, signal_complete_, signal_error_);
  }
#ifdef ENABLE_FILE_SCHEME
  inline RefFileConnection req(RefFileRequest request) {
    return req(request, signal_complete_, signal_error_);
  }
#endif
  inline RefConnection req(const URL         &url,
                           const std::string &charset = std::string()) {
    return req(url, signal_complete_, signal_error_, charset);
  }

  RefHTTPResponse req_sync(RefHTTPRequest request);
#ifdef ENABLE_FILE_SCHEME
  RefFileResponse req_sync(RefFileRequest request);
#endif
  RefResponse req_sync(const URL         &url,
                       const std::string &charset);

  Pool(Application &app);
  virtual ~Pool();
};

}/*loader*/
}/*gpyp*/

#endif/*LOADER_H_*/
